/*
 * contrib/btree_gist/btree_interval.c
 */
#include "postgres.h"

#include "btree_gist.h"
#include "btree_utils_num.h"
#include "utils/builtins.h"
#include "utils/timestamp.h"

typedef struct
{
	Interval	lower,
				upper;
} intvKEY;


/*
** 区间操作
*/
PG_FUNCTION_INFO_V1(gbt_intv_compress);
PG_FUNCTION_INFO_V1(gbt_intv_fetch);
PG_FUNCTION_INFO_V1(gbt_intv_decompress);
PG_FUNCTION_INFO_V1(gbt_intv_union);
PG_FUNCTION_INFO_V1(gbt_intv_picksplit);
PG_FUNCTION_INFO_V1(gbt_intv_consistent);
PG_FUNCTION_INFO_V1(gbt_intv_distance);
PG_FUNCTION_INFO_V1(gbt_intv_penalty);
PG_FUNCTION_INFO_V1(gbt_intv_same);


static bool fc_gbt_intvgt(const void *fc_a, const void *fc_b, FmgrInfo *fc_flinfo)
{
	return DatumGetBool(DirectFunctionCall2(interval_gt, IntervalPGetDatum(fc_a), IntervalPGetDatum(fc_b)));
}

static bool fc_gbt_intvge(const void *fc_a, const void *fc_b, FmgrInfo *fc_flinfo)
{
	return DatumGetBool(DirectFunctionCall2(interval_ge, IntervalPGetDatum(fc_a), IntervalPGetDatum(fc_b)));
}

static bool fc_gbt_intveq(const void *fc_a, const void *fc_b, FmgrInfo *fc_flinfo)
{
	return DatumGetBool(DirectFunctionCall2(interval_eq, IntervalPGetDatum(fc_a), IntervalPGetDatum(fc_b)));
}

static bool fc_gbt_intvle(const void *fc_a, const void *fc_b, FmgrInfo *fc_flinfo)
{
	return DatumGetBool(DirectFunctionCall2(interval_le, IntervalPGetDatum(fc_a), IntervalPGetDatum(fc_b)));
}

static bool fc_gbt_intvlt(const void *fc_a, const void *fc_b, FmgrInfo *fc_flinfo)
{
	return DatumGetBool(DirectFunctionCall2(interval_lt, IntervalPGetDatum(fc_a), IntervalPGetDatum(fc_b)));
}

static int fc_gbt_intvkey_cmp(const void *fc_a, const void *fc_b, FmgrInfo *fc_flinfo)
{
	intvKEY    *fc_ia = (intvKEY *) (((const Nsrt *) fc_a)->t);
	intvKEY    *fc_ib = (intvKEY *) (((const Nsrt *) fc_b)->t);
	int			fc_res;

	fc_res = DatumGetInt32(DirectFunctionCall2(interval_cmp, IntervalPGetDatum(&fc_ia->lower), IntervalPGetDatum(&fc_ib->lower)));
	if (fc_res == 0)
		return DatumGetInt32(DirectFunctionCall2(interval_cmp, IntervalPGetDatum(&fc_ia->upper), IntervalPGetDatum(&fc_ib->upper)));

	return fc_res;
}


static double fc_intr2num(const Interval *fc_i)
{
	return INTERVAL_TO_SEC(fc_i);
}

static float8 fc_gbt_intv_dist(const void *fc_a, const void *fc_b, FmgrInfo *fc_flinfo)
{
	return (float8) Abs(fc_intr2num((const Interval *) fc_a) - fc_intr2num((const Interval *) fc_b));
}

/*
 * INTERVALSIZE 应该是 Interval 在磁盘上的实际大小，如 pg_type 中所示。
 * 如果编译器在结构体末尾坚持添加对齐填充，这可能小于 sizeof(Interval)。
 * （注意：这个问题在当前的 Interval 定义中已经过时，但在添加独立的“天”字段之前是实际存在的。）
 */
#define INTERVALSIZE 16

static const gbtree_ninfo tinfo =
{
	gbt_t_intv,
	sizeof(Interval),
	32,							/* sizeof(gbtreekey32) */
	fc_gbt_intvgt,
	fc_gbt_intvge,
	fc_gbt_intveq,
	fc_gbt_intvle,
	fc_gbt_intvlt,
	fc_gbt_intvkey_cmp,
	fc_gbt_intv_dist
};


Interval *
abs_interval(Interval *fc_a)
{
	static Interval fc_zero = {0, 0, 0};

	if (DatumGetBool(DirectFunctionCall2(interval_lt,
										 IntervalPGetDatum(fc_a),
										 IntervalPGetDatum(&fc_zero))))
		fc_a = DatumGetIntervalP(DirectFunctionCall1(interval_um,
												  IntervalPGetDatum(fc_a)));

	return fc_a;
}

PG_FUNCTION_INFO_V1(interval_dist);
Datum interval_dist(PG_FUNCTION_ARGS)
{
	Datum		fc_diff = DirectFunctionCall2(interval_mi,
										   PG_GETARG_DATUM(0),
										   PG_GETARG_DATUM(1));

	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(fc_diff)));
}


/**************************************************
 * 区间操作
 **************************************************/


Datum gbt_intv_compress(PG_FUNCTION_ARGS)
{
	GISTENTRY  *fc_entry = (GISTENTRY *) PG_GETARG_POINTER(0);
	GISTENTRY  *fc_retval = fc_entry;

	if (fc_entry->leafkey || INTERVALSIZE != sizeof(Interval))
	{
		char	   *fc_r = (char *) palloc(2 * INTERVALSIZE);

		fc_retval = palloc(sizeof(GISTENTRY));

		if (fc_entry->leafkey)
		{
			Interval   *fc_key = DatumGetIntervalP(fc_entry->key);

			memcpy((void *) fc_r, (void *) fc_key, INTERVALSIZE);
			memcpy((void *) (fc_r + INTERVALSIZE), (void *) fc_key, INTERVALSIZE);
		}
		else
		{
			intvKEY    *fc_key = (intvKEY *) DatumGetPointer(fc_entry->key);

			memcpy(fc_r, &fc_key->lower, INTERVALSIZE);
			memcpy(fc_r + INTERVALSIZE, &fc_key->upper, INTERVALSIZE);
		}
		gistentryinit(*fc_retval, PointerGetDatum(fc_r),
					  fc_entry->rel, fc_entry->page,
					  fc_entry->offset, false);
	}

	PG_RETURN_POINTER(fc_retval);
}

Datum gbt_intv_fetch(PG_FUNCTION_ARGS)
{
	GISTENTRY  *fc_entry = (GISTENTRY *) PG_GETARG_POINTER(0);

	PG_RETURN_POINTER(gbt_num_fetch(fc_entry, &tinfo));
}

Datum gbt_intv_decompress(PG_FUNCTION_ARGS)
{
	GISTENTRY  *fc_entry = (GISTENTRY *) PG_GETARG_POINTER(0);
	GISTENTRY  *fc_retval = fc_entry;

	if (INTERVALSIZE != sizeof(Interval))
	{
		intvKEY    *fc_r = palloc(sizeof(intvKEY));
		char	   *fc_key = DatumGetPointer(fc_entry->key);

		fc_retval = palloc(sizeof(GISTENTRY));
		memcpy(&fc_r->lower, fc_key, INTERVALSIZE);
		memcpy(&fc_r->upper, fc_key + INTERVALSIZE, INTERVALSIZE);

		gistentryinit(*fc_retval, PointerGetDatum(fc_r),
					  fc_entry->rel, fc_entry->page,
					  fc_entry->offset, false);
	}
	PG_RETURN_POINTER(fc_retval);
}


Datum gbt_intv_consistent(PG_FUNCTION_ARGS)
{
	GISTENTRY  *fc_entry = (GISTENTRY *) PG_GETARG_POINTER(0);
	Interval   *fc_query = PG_GETARG_INTERVAL_P(1);
	StrategyNumber fc_strategy = (StrategyNumber) PG_GETARG_UINT16(2);

	/* Oid		subtype = PG_GETARG_OID(3); */
	bool	   *fc_recheck = (bool *) PG_GETARG_POINTER(4);
	intvKEY    *fc_kkk = (intvKEY *) DatumGetPointer(fc_entry->key);
	GBT_NUMKEY_R fc_key;

	/* 由此函数服务的所有案例都是精确的 */
	*fc_recheck = false;

	fc_key.lower = (GBT_NUMKEY *) &fc_kkk->lower;
	fc_key.upper = (GBT_NUMKEY *) &fc_kkk->upper;

	PG_RETURN_BOOL(gbt_num_consistent(&fc_key, (void *) fc_query, &fc_strategy,
									  GIST_LEAF(fc_entry), &tinfo, fcinfo->flinfo));
}


Datum gbt_intv_distance(PG_FUNCTION_ARGS)
{
	GISTENTRY  *fc_entry = (GISTENTRY *) PG_GETARG_POINTER(0);
	Interval   *fc_query = PG_GETARG_INTERVAL_P(1);

	/* Oid		subtype = PG_GETARG_OID(3); */
	intvKEY    *fc_kkk = (intvKEY *) DatumGetPointer(fc_entry->key);
	GBT_NUMKEY_R fc_key;

	fc_key.lower = (GBT_NUMKEY *) &fc_kkk->lower;
	fc_key.upper = (GBT_NUMKEY *) &fc_kkk->upper;

	PG_RETURN_FLOAT8(gbt_num_distance(&fc_key, (void *) fc_query, GIST_LEAF(fc_entry),
									  &tinfo, fcinfo->flinfo));
}


Datum gbt_intv_union(PG_FUNCTION_ARGS)
{
	GistEntryVector *fc_entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
	void	   *fc_out = palloc(sizeof(intvKEY));

	*(int *) PG_GETARG_POINTER(1) = sizeof(intvKEY);
	PG_RETURN_POINTER(gbt_num_union((void *) fc_out, fc_entryvec, &tinfo, fcinfo->flinfo));
}


Datum gbt_intv_penalty(PG_FUNCTION_ARGS)
{
	intvKEY    *fc_origentry = (intvKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key);
	intvKEY    *fc_newentry = (intvKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key);
	float	   *fc_result = (float *) PG_GETARG_POINTER(2);
	double		fc_iorg[2],
				fc_inew[2];

	fc_iorg[0] = fc_intr2num(&fc_origentry->lower);
	fc_iorg[1] = fc_intr2num(&fc_origentry->upper);
	fc_inew[0] = fc_intr2num(&fc_newentry->lower);
	fc_inew[1] = fc_intr2num(&fc_newentry->upper);

	penalty_num(fc_result, fc_iorg[0], fc_iorg[1], fc_inew[0], fc_inew[1]);

	PG_RETURN_POINTER(fc_result);
}

Datum gbt_intv_picksplit(PG_FUNCTION_ARGS)
{
	PG_RETURN_POINTER(gbt_num_picksplit((GistEntryVector *) PG_GETARG_POINTER(0),
										(GIST_SPLITVEC *) PG_GETARG_POINTER(1),
										&tinfo, fcinfo->flinfo));
}

Datum gbt_intv_same(PG_FUNCTION_ARGS)
{
	intvKEY    *fc_b1 = (intvKEY *) PG_GETARG_POINTER(0);
	intvKEY    *fc_b2 = (intvKEY *) PG_GETARG_POINTER(1);
	bool	   *fc_result = (bool *) PG_GETARG_POINTER(2);

	*fc_result = gbt_num_same((void *) fc_b1, (void *) fc_b2, &tinfo, fcinfo->flinfo);
	PG_RETURN_POINTER(fc_result);
}
